; 将一些公共函数放入到此
[SECTION .protectModeFunc]
[BITS 32]

;===============================================
; 以十六进制方式显示一个整数，从左到右分别是最高位-最低位
; 入参：
;   eax = 待显示的整数
;   edi = 向下一个要显示的位置
; 出参:
;   无
DispInt:
    push edx

    mov edx, eax

    shr eax, 24
    call DispByteByHex

    mov eax, edx
    shr eax, 16
    call DispByteByHex

    mov eax, edx
    shr eax, 8
    call DispByteByHex

    mov eax, edx
    call DispByteByHex

    ;add edi, 2      ; 相当于显示完后，空出一个位置（有空格的效果）

    pop edx
    ret

; ==================================================
; 以十六进制方式显示一个字节 --- 将数转换成字符后显示
; 例如al = 1010 0010，则显示为对应的字符"A2"
; 默认数字已经存放在AL寄存器中，edi已经指向下一个要显示的位置
; 被改变的寄存器：ax,edi
DispByteByHex:
    push ecx
    push edx

    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov dl, al      ; 将al内容保存起来
    shr al, 4       ; al右移4位，相当于获取al的高4位，先把高4位转换成字符显示出来
    mov ecx, 2      ; ecx = 2, loop循环2次，因为要将al（字节）的高位和地位分2次显示出来
.begin:
    and al, 01111b  ; 获取al的低4位，第一次循环时候，al的值是函数调用传入al的高4位，第二次循环的时候al的值是函数调用传入的al的低4位
    cmp al, 9       ; 数字与9进行比较
    ja .1           ; >9 则跳转到.1执行
    add al, '0'     ; <=9，转换成对应的字符，例如4 -> ‘4’
    jmp .2          ; 跳转到显示字符的地方
.1:
    sub al, 0ah     ; 对于大于等于10的数字，先减去10
    add al, 'A'     ; 然后转换成对应的十六进制数字，例如 13 = (13 - 10) + 'A' -> 'D'
.2:
    mov [gs:edi], ax    ; ah保存字的性质（黑底红字），al保存转换成字符数字，显示出来
    add edi, 2          ; 显示的位置偏移

    mov al, dl      ; 获取原al的值，实际上是取原al的低4位，因为高4位在第一次循环已经做了显示处理
    loop .begin

    ;add edi, 2      ; 相当于显示完AL对应的两个字符后，空出一个位置（有空格的效果）

    pop edx
    pop ecx
    ret
; DispByteByHex 结束


; ==================================================
; 清屏操作：将字符都赋为黑底' '
; 入参：
;   无
; 出参:
;   无
CleanScreen:
    push edi

    xor edi, edi
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov al, ' '     ; 空字符
    mov ecx, 2000   ; 循环次数，按照80 * 25 = 2000次
.loop_c:
    mov [gs:edi], ax
    add edi, 2
    loop .loop_c

    pop edi
    ret


; ==================================================
; 显示字符串
; 入参：
;   eax = 字符串起始位置(也就是ds:eax)
;   ecx = 待显示字符串长度
;   edi = 下一个要显示的位置
; 出参:
;   无
DispStr:
    push esi
    mov esi, eax
    mov ah, 0ch     ; 0000 黑底， 000C 红字
.loop:
    mov al, byte [ds:esi]
    mov [gs:edi], ax
    add edi, 2
    inc esi
    loop .loop
    pop esi
    ret


;===============================================
; 显示一个空格
; 入参：
;   edi = 向下一个要显示的位置
DispSpace:
    push eax
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov al, ' '     ; 空字符
    mov [gs:edi], ax
    add edi, 2
    pop eax
    ret

;===============================================
; 将显示位置移动到下一行的起始位置
; 入参：
;   无
; 出参：
;   edi = 下一行的起始位置
DispReturn:
    push eax

    mov eax, edi
    mov bl, 160     ; 每一行80个字符+80个字符的属性，总计160个字符
    div bl          ; eax=eax/bl，表示当前总共有几行

    and eax, 0FFh
    inc eax         ; 将显示的行数+1

    mov bl, 160
    mul bl          ; 显示的行数 * 每行的字符数量 = 下一个要显示的位置

    mov edi, eax    ; 将下一行显示的位置存储到edi供其他程序使用

    pop eax
    ret
; DispReturn 结束


;===============================================
; 将指定长度的数据复制到指定长度的目的位置中
; 入参：
;   ds:esi = 待复制的源数据地址
;   ecx = 待复制的数据长度(单位是字节)
;   es:edi = 数据存放的目的地址
;   edx = 目的地址的最大长度(单位是字节)
; 出参：
;   eax = 复制的长度，出错时候值为-1
MemCpy:
    push esi
    push edi

    ; 此处暂时忽略源地址和目的地址的检查

    ; 检查长度
    cmp ecx, edx
    ja .length_cmp_err

    xor eax, eax
.loop_cpy:
    mov al, byte [ds:esi]
    stosb
    inc esi
    loop .loop_cpy
    jmp .copy_ok

.length_cmp_err:
    mov eax, -1
    
.copy_ok:
    mov eax, ecx

    pop edi
    pop esi
    ret

